package com.docmgmt.mvp.service.impl;

import com.docmgmt.mvp.dto.FileVO;
import com.docmgmt.mvp.dto.TagVO;
import com.docmgmt.mvp.entity.FileEntity;
import com.docmgmt.mvp.entity.FileTagEntity;
import com.docmgmt.mvp.entity.TagEntity;
import com.docmgmt.mvp.exception.BusinessException;
import com.docmgmt.mvp.exception.ForbiddenException;
import com.docmgmt.mvp.exception.NotFoundException;
import com.docmgmt.mvp.mapper.FileMapper;
import com.docmgmt.mvp.mapper.FileTagMapper;
import com.docmgmt.mvp.mapper.TagMapper;
import com.docmgmt.mvp.service.PermissionService;
import com.docmgmt.mvp.service.TagService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 标签表 服务实现类
 * </p>
 *
 * @author Dora
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TagServiceImpl extends ServiceImpl<TagMapper, TagEntity> implements TagService {

    private final TagMapper tagMapper;
    private final FileTagMapper fileTagMapper;
    private final FileMapper fileMapper;
    private final PermissionService permissionService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TagVO createTag(String name, String color, Long userId) {
        log.info("Creating tag: name={}, userId={}", name, userId);

        // Check if tag already exists
        TagEntity existingTag = tagMapper.selectByName(name);
        if (existingTag != null) {
            throw new BusinessException("Tag already exists");
        }

        // Create tag
        TagEntity tag = TagEntity.builder()
                .name(name)
                .color(color != null ? color : "#999999")
                .createdBy(userId)
                .createdAt(LocalDateTime.now())
                .build();

        tagMapper.insert(tag);

        log.info("Tag created: id={}, name={}", tag.getId(), tag.getName());

        return entityToVO(tag);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteTag(Long tagId, Long userId) {
        log.info("Deleting tag: tagId={}, userId={}", tagId, userId);

        // Query tag
        TagEntity tag = tagMapper.selectById(tagId);
        if (tag == null) {
            throw new NotFoundException("Tag not found");
        }

        // Check permission (only creator can delete)
        if (!tag.getCreatedBy().equals(userId)) {
            throw new ForbiddenException("Only tag creator can delete it");
        }

        // Delete all file-tag associations
        fileTagMapper.deleteByTagId(tagId);

        // Delete tag
        tagMapper.deleteById(tagId);

        log.info("Tag deleted: tagId={}", tagId);
    }

    @Override
    public List<TagVO> listUserTags(Long userId) {
        log.info("Listing user tags: userId={}", userId);

        List<TagEntity> tags = tagMapper.listByCreator(userId);

        return tags.stream()
                .map(this::entityToVO)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<TagVO> addFileTag(Long fileId, List<Long> tagIds, Long userId) {
        log.info("Adding tags to file: fileId={}, tagIds={}, userId={}", fileId, tagIds, userId);

        // Check file exists
        FileEntity file = fileMapper.selectById(fileId);
        if (file == null) {
            throw new NotFoundException("File not found");
        }

        // Check permission
        if (!permissionService.checkPermission(userId, "file:" + fileId, "update")) {
            throw new ForbiddenException("No permission to modify this file");
        }

        List<TagVO> addedTags = new ArrayList<>();

        for (Long tagId : tagIds) {
            // Check tag exists
            TagEntity tag = tagMapper.selectById(tagId);
            if (tag == null) {
                log.warn("Tag not found: tagId={}", tagId);
                continue;
            }

            // Check if association already exists
            FileTagEntity existing = fileTagMapper.selectByFileAndTag(fileId, tagId);
            if (existing != null) {
                log.info("File-tag association already exists: fileId={}, tagId={}", fileId, tagId);
                addedTags.add(entityToVO(tag));
                continue;
            }

            // Create association
            FileTagEntity fileTag = FileTagEntity.builder()
                    .fileId(fileId)
                    .tagId(tagId)
                    .createdAt(LocalDateTime.now())
                    .build();

            fileTagMapper.insert(fileTag);
            addedTags.add(entityToVO(tag));

            log.info("Tag added to file: fileId={}, tagId={}", fileId, tagId);
        }

        return addedTags;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeFileTag(Long fileId, Long tagId, Long userId) {
        log.info("Removing tag from file: fileId={}, tagId={}, userId={}", fileId, tagId, userId);

        // Check file exists
        FileEntity file = fileMapper.selectById(fileId);
        if (file == null) {
            throw new NotFoundException("File not found");
        }

        // Check permission
        if (!permissionService.checkPermission(userId, "file:" + fileId, "update")) {
            throw new ForbiddenException("No permission to modify this file");
        }

        // Remove association
        int deleted = fileTagMapper.deleteByFileAndTag(fileId, tagId);

        if (deleted == 0) {
            throw new NotFoundException("File-tag association not found");
        }

        log.info("Tag removed from file: fileId={}, tagId={}", fileId, tagId);
    }

    @Override
    public List<TagVO> listFileTags(Long fileId) {
        log.info("Listing file tags: fileId={}", fileId);

        // Check file exists
        FileEntity file = fileMapper.selectById(fileId);
        if (file == null) {
            throw new NotFoundException("File not found");
        }

        List<TagEntity> tags = tagMapper.listByFileId(fileId);

        return tags.stream()
                .map(this::entityToVO)
                .collect(Collectors.toList());
    }

    @Override
    public List<FileVO> listFilesByTag(Long tagId, Long userId) {
        log.info("Listing files by tag: tagId={}, userId={}", tagId, userId);

        // Check tag exists
        TagEntity tag = tagMapper.selectById(tagId);
        if (tag == null) {
            throw new NotFoundException("Tag not found");
        }

        // Query all file IDs with this tag
        List<Long> fileIds = fileTagMapper.selectFileIdsByTagId(tagId);

        if (fileIds.isEmpty()) {
            return new ArrayList<>();
        }

        // Query files and filter by permission
        List<FileEntity> files = fileMapper.selectBatchIds(fileIds);

        return files.stream()
                .filter(file -> permissionService.checkPermission(userId, "file:" + file.getId(), "read"))
                .map(this::fileEntityToVO)
                .collect(Collectors.toList());
    }

    /**
     * Entity to VO
     */
    private TagVO entityToVO(TagEntity entity) {
        return TagVO.builder()
                .id(entity.getId())
                .name(entity.getName())
                .color(entity.getColor())
                .createdBy(entity.getCreatedBy())
                .createdByName("Unknown User")
                .createdAt(entity.getCreatedAt() != null ?
                        entity.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) : null)
                .fileCount(0)  // TODO: calculate file count if needed
                .build();
    }

    /**
     * FileEntity to FileVO
     */
    private FileVO fileEntityToVO(FileEntity entity) {
        return FileVO.builder()
                .id(entity.getId())
                .name(entity.getFileName())
                .folderId(entity.getFolderId())
                .size(entity.getFileSize())
                .sizeDisplay(formatFileSize(entity.getFileSize()))
                .extension(entity.getFileExtension())
                .mimeType(entity.getMimeType())
                .uploadedBy(entity.getUploadedBy())
                .uploadedByName("Unknown User")
                .createdAt(entity.getCreatedAt() != null ?
                        entity.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) : null)
                .updatedAt(entity.getUpdatedAt() != null ?
                        entity.getUpdatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) : null)
                .build();
    }

    /**
     * Format file size
     */
    private String formatFileSize(Long size) {
        if (size == null || size == 0) {
            return "0 B";
        }
        String[] units = {"B", "KB", "MB", "GB", "TB"};
        int unitIndex = 0;
        double sizeInUnit = size.doubleValue();

        while (sizeInUnit >= 1024 && unitIndex < units.length - 1) {
            sizeInUnit /= 1024;
            unitIndex++;
        }

        return String.format("%.2f %s", sizeInUnit, units[unitIndex]);
    }
}
